// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"internal/bytealg"
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

// 此文件中的代码为所有体系结构实现堆栈跟踪遍历。
// 关于给定体系结构，最重要的事实是它是否使用链接寄存器。
// 在具有链接寄存器的系统上，非叶函数的序言将LR的传入值存储在新分配的堆栈帧底部。
// 在没有链接寄存器（x86）的系统上，体系结构在调用指令期间推送返回PC，因此返回PC最终位于堆栈帧之上。
// 在此文件中，返回的PC始终称为LR，无论它是如何找到的。

const usesLR = sys.MinFrameSize > 0

// 对延迟函数调用的回溯。
// 将它们报告为已调用但尚未开始执行的调用。
func tracebackdefers(gp *g, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer) {
	var frame stkframe
	for d := gp._defer; d != nil; d = d.link {
		fn := d.fn
		if fn == nil {
			// nil函数的延迟。Args不重要。
			frame.pc = 0
			frame.fn = funcInfo{}
			frame.argp = 0
			frame.arglen = 0
			frame.argmap = nil
		} else {
			frame.pc = fn.fn
			f := findfunc(frame.pc)
			if !f.valid() {
				print("runtime: unknown pc in defer ", hex(frame.pc), "\n")
				throw("unknown pc")
			}
			frame.fn = f
			frame.argp = uintptr(deferArgs(d))
			var ok bool
			frame.arglen, frame.argmap, ok = getArgInfoFast(f, true)
			if !ok {
				frame.arglen, frame.argmap = getArgInfo(&frame, f, true, fn)
			}
		}
		frame.continpc = frame.pc
		if !callback((*stkframe)(noescape(unsafe.Pointer(&frame))), v) {
			return
		}
	}
}

// 通用回溯。处理运行时堆栈打印（pcbuf==nil）、
// 运行时调用函数（pcbuf！=nil）以及垃圾
// 收集器（回调！=nil）。合并这些代码有点笨拙，但避免了
// 重复代码及其所有细微之处。
// 
// skip参数仅对pcbuf有效！=nil，并计算要跳过的逻辑帧而不是物理帧的数量
// （使用内联，pcbuf中的
// PC可以表示多个调用）。
func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max int, callback func(*stkframe, unsafe.Pointer) bool, v unsafe.Pointer, flags uint) int {
	if skip > 0 && callback != nil {
		throw("gentraceback callback cannot be used with non-zero skip")
	}

	// 不要称之为“g”；很容易混淆“g”和“gp”。
	if ourg := getg(); ourg == gp && ourg == ourg.m.curg {
		// 启动sp已作为uintptr传入，调用方可能也有其他uintptr类型的堆栈引用。
		// 如果在我们到达这里的某个调用期间或在某个
		// 堆栈下的回调必须增长，那么所有这些uintptr对堆栈的引用
		// 将不会更新，gentraceback将继续
		// 检查旧堆栈内存，该内存可能不再有效。
		// 即使所有变量都已正确更新，我们也不清楚
		// 是否要公开一个从一个堆栈开始到另一个堆栈结束的回溯。这可能会让打电话的人感到困惑。
		// 相反，我们要求gentraceback和任何其他
		// 为当前goroutine（通常由
		// 调用getcallersp获得）接受sp的函数不能在该goroutine的堆栈上运行，而应在g0堆栈上运行
		// 。
		throw("gentraceback cannot trace user goroutine on its own stack")
	}
	level, _, _ := gotraceback()

	var ctxt *funcval // 未启动的goroutine的上下文指针。见第25897期。

	if pc0 == ^uintptr(0) && sp0 == ^uintptr(0) { // 从gp获取保存值的信号。
		if gp.syscallsp != 0 {
			pc0 = gp.syscallpc
			sp0 = gp.syscallsp
			if usesLR {
				lr0 = 0
			}
		} else {
			pc0 = gp.sched.pc
			sp0 = gp.sched.sp
			if usesLR {
				lr0 = gp.sched.lr
			}
			ctxt = (*funcval)(gp.sched.ctxt)
		}
	}

	nprint := 0
	var frame stkframe
	frame.pc = pc0
	frame.sp = sp0
	if usesLR {
		frame.lr = lr0
	}
	waspanic := false
	cgoCtxt := gp.cgoCtxt
	printing := pcbuf == nil && callback == nil

	// 如果PC为零，则可能是一个nil函数调用。
	// 从调用方的帧开始。
	if frame.pc == 0 {
		if usesLR {
			frame.pc = *(*uintptr)(unsafe.Pointer(frame.sp))
			frame.lr = 0
		} else {
			frame.pc = uintptr(*(*uintptr)(unsafe.Pointer(frame.sp)))
			frame.sp += sys.PtrSize
		}
	}

	f := findfunc(frame.pc)
	if !f.valid() {
		if callback != nil || printing {
			print("runtime: unknown pc ", hex(frame.pc), "\n")
			tracebackHexdump(gp.stack, &frame, 0)
		}
		if callback != nil {
			throw("unknown pc")
		}
		return 0
	}
	frame.fn = f

	var cache pcvalueCache

	lastFuncID := funcID_normal
	n := 0
	for n < max {
		// 通常：
		// pc是运行函数的pc。
		// sp是指向该程序计数器的堆栈指针。
		// fp是该程序计数器上的帧指针（调用方的堆栈指针），如果未知，则为nil。
		// stk是包含sp的堆栈。
		// 调用方的程序计数器是lr，除非lr为零，在这种情况下它是*（uintptpr*）sp。
		f = frame.fn
		if f.pcsp == 0 {
			// 无帧信息，必须是外部函数，如种族支持。
			// 见golang.org/issue/13568。
			break
		}

		// 计算函数信息标志。
		flag := f.flag
		if f.funcID == funcID_cgocallback {
			// cgocallback确实写入SP以从g0切换到curg堆栈，
			// 但它仔细安排，在转换过程中，两个堆栈
			// 都具有有效的cgocallback帧，以便通过解绕。
			// 因此我们不需要将其与其他SP写入函数一起排除。
			flag &^= funcFlag_SPWRITE
		}
		if frame.pc == pc0 && frame.sp == sp0 && pc0 == gp.syscallpc && sp0 == gp.syscallsp {
			// 一些Syscall函数会写入SP，但它们只有在
			// 使用entersycall保存条目PC/SP后才会写入SP。
			// 因为我们使用的是PC/SP条目，后面的SP写入并不重要。
			flag &^= funcFlag_SPWRITE
		}

		// 找到了一个实际函数。
		// 派生帧指针和链接寄存器。
		if frame.fp == 0 {
			// 跳过系统堆栈转换。如果我们在g0上，有一个用户
			// goroutine，请尝试跳转。否则这是一个常规电话。
			if flags&_TraceJumpStack != 0 && gp == gp.m.g0 && gp.m.curg != nil {
				switch f.funcID {
				case funcID_morestack:
					// morestack无法正常返回--newstack（）
					// gogo's to curg.sched。与之匹配。
					// 这可以防止morestack（）出现在回溯中，
					// 但这是有道理的，因为它永远不会返回给
					// 。
					frame.pc = gp.m.curg.sched.pc
					frame.fn = findfunc(frame.pc)
					f = frame.fn
					flag = f.flag
					frame.sp = gp.m.curg.sched.sp
					cgoCtxt = gp.m.curg.cgoCtxt
				case funcID_systemstack:
					// systemstack正常返回，所以只需执行
					// 堆栈转换即可。
					frame.sp = gp.m.curg.sched.sp
					cgoCtxt = gp.m.curg.cgoCtxt
					flag &^= funcFlag_SPWRITE
				}
			}
			frame.fp = frame.sp + uintptr(funcspdelta(f, frame.pc, &cache))
			if !usesLR {
				// 在x86上，call指令在进入新函数之前推送返回PC。
				frame.fp += sys.PtrSize
			}
		}
		var flr funcInfo
		if flag&funcFlag_TOPFRAME != 0 {
			// 此函数标记堆栈的顶部。停止追踪。
			frame.lr = 0
			flr = funcInfo{}
		} else if flag&funcFlag_SPWRITE != 0 && (callback == nil || n > 0) {
			// 我们使用的函数对SP进行写入，但我们不知道如何在spdelta表中进行编码。示例包括上下文
			// 切换例程（如runtime.gogo），以及将
			// 切换到g0堆栈以运行主机C代码的任何代码。由于我们无法可靠地释放SP（我们甚至可能不在我们认为的堆栈上），我们在这里停止回溯。
			// 这仅适用于分析信号（回调==nil）。
			// 
			// 对于GC堆栈遍历（回调！=nil），我们应该只看到
			// 在堆栈增长检查期间主动抢占了条目
			// 的函数。在这种情况下，函数具有
			// 尚未有机会对SP执行任何写入操作，因此可以安全地解除。
			// isAsyncSafePoint不允许程序集函数被异步抢占，
			// 和preemptPark双重检查SPWRITE函数是否未被异步抢占。
			// 因此，对于GC堆栈遍历，我们将在堆栈的底部框架中不执行任何操作（如果主体不为n==0执行此操作）。但再往上看，我们最好不要找到任何。
			if callback != nil {
				println("traceback: unexpected SPWRITE function", funcname(f))
				throw("traceback")
			}
			frame.lr = 0
			flr = funcInfo{}
		} else {
			var lrPtr uintptr
			if usesLR {
				if n == 0 && frame.sp < frame.fp || frame.lr == 0 {
					lrPtr = frame.sp
					frame.lr = *(*uintptr)(unsafe.Pointer(lrPtr))
				}
			} else {
				if frame.lr == 0 {
					lrPtr = frame.fp - sys.PtrSize
					frame.lr = uintptr(*(*uintptr)(unsafe.Pointer(lrPtr)))
				}
			}
			flr = findfunc(frame.lr)
			if !flr.valid() {
				// 如果您在错误的时间得到分析中断，就会发生这种情况。
				// 在这种情况下，提前停止是可以的。
				// 但如果设置了回调，我们正在进行垃圾收集，必须获取所有内容，所以大声崩溃。
				doPrint := printing
				if doPrint && gp.m.incgo && f.funcID == funcID_sigpanic {
					// 我们可以将Sigmanic 
					// 调用直接注入C代码，
					// 在这种情况下，我们将看到一个C 
					// 返回PC。不要抱怨。
					doPrint = false
				}
				if callback != nil || doPrint {
					print("runtime: unexpected return pc for ", funcname(f), " called from ", hex(frame.lr), "\n")
					tracebackHexdump(gp.stack, &frame, lrPtr)
				}
				if callback != nil {
					throw("unknown caller pc")
				}
			}
		}

		frame.varp = frame.fp
		if !usesLR {
			// 在x86上，call指令在进入新函数之前推送返回PC。
			frame.varp -= sys.PtrSize
		}

		// 对于带有帧指针的体系结构，如果有
		// 一个帧，那么这里有一个保存的帧指针。
		// 
		// 注意：此代码并不像看上去那样通用。
		// 在x86上，ABI是将帧指针字保存在堆栈帧的
		// 顶部，因此我们必须返回到它上面。
		// 在arm64上，帧指针应该位于堆栈的底部（R29（aka FP）=RSP），在这种情况下，我们不想在这里进行减法运算。但是我们一开始没有任何帧指针，当我们想要添加它时，我们不想中断所有直接写入8（RSP）的程序集，而是想将第一个参数设置为调用的函数。
		// 因此我们决定将FP link*写在堆栈指针
		// （在Go函数中R29=RSP-8）。
		// 这在技术上与ABI兼容，但不是标准配置。
		// 它恰好模仿了x86布局。
		// 其他架构可能会做出不同的决定。
		if frame.varp > frame.sp && framepointer_enabled {
			frame.varp -= sys.PtrSize
		}

		// 导出参数的大小。
		// 大多数函数都有一个固定大小的参数块，
		// 因此我们可以使用函数f的元数据。
		// 但不是全部：在包运行时和反射中有一些可变函数
		// 对于这些函数，我们使用调用方记录的特定于调用的
		// 元数据。
		if callback != nil || printing {
			frame.argp = frame.fp + sys.MinFrameSize
			var ok bool
			frame.arglen, frame.argmap, ok = getArgInfoFast(f, callback != nil)
			if !ok {
				frame.arglen, frame.argmap = getArgInfo(&frame, f, callback != nil, ctxt)
			}
		}
		ctxt = nil // ctxt仅用于获取最顶层帧的参数映射

		// 确定帧的“continuation PC”，以便继续。
		// 通常这是堆栈上的返回地址，但如果sigpanic 
		// 位于堆栈上此函数的正下方，则帧
		// 由于陷阱而停止执行，frame.pc可能不是
		// 查找活动信息的安全点。在这种令人恐慌的情况下，
		// 函数要么根本不返回（如果它没有延迟，或者如果
		// 延迟没有恢复），要么从对
		// deferproc的一次调用返回（如果相应的延迟函数恢复）。
		// 在后一种情况下，使用deferreturn调用站点作为延续pc。
		frame.continpc = frame.pc
		if waspanic {
			if frame.fn.deferreturn != 0 {
				frame.continpc = frame.fn.entry + uintptr(frame.fn.deferreturn) + 1
				// 注意：这可能会使返回变量比
				// 保持更长的生存时间，这是严格必要的，因为我们使用“函数有一个defer语句”
				// 作为“函数实际延迟了某些内容”的代理。这似乎是一个小缺点。（我们过去常常通过
				// gp.\u defer查找与此函数对应的defer，但该
				// 在堆栈复制过程中很难处理堆栈上的延迟记录。）
				// 注意：+1用于偏移
				// 堆栈的-1。go:getStackMap用于备份返回
				// 地址确保pc位于调用指令中。
			} else {
				frame.continpc = 0
			}
		}

		if callback != nil {
			if !callback((*stkframe)(noescape(unsafe.Pointer(&frame))), v) {
				return n
			}
		}

		if pcbuf != nil {
			pc := frame.pc
			// 备份到调用指令以读取内联信息（逻辑与下面相同）
			tracepc := pc
			// 通常，pc是一个返回地址。在这种情况下，我们希望使用pc-1查找
			// 文件/行信息，因为这是
			// call指令的pc（更准确地说，是call指令的最后一个字节）。
			// 调用方希望pc缓冲区包含返回地址，而
			// 本身也会包含返回地址，因此我们保持pc不变。
			// 当pc来自信号（如探查器或segv）时，我们希望
			// 使用pc查找文件/行信息，并将pc+1存储在
			// pc缓冲区中，以便呼叫者在查找前无条件减去1。
			// 见第34123期。
			// 在没有实际运行代码（如runtime.mstart）的情况下初始化帧时，pc可以处于函数入口。
			if (n == 0 && flags&_TraceTrap != 0) || waspanic || pc == f.entry {
				pc++
			} else {
				tracepc--
			}

			// 如果有内联信息，则记录内部帧。
			if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
				inltree := (*[1 << 20]inlinedCall)(inldata)
				for {
					ix := pcdatavalue(f, _PCDATA_InlTreeIndex, tracepc, &cache)
					if ix < 0 {
						break
					}
					if inltree[ix].funcID == funcID_wrapper && elideWrapperCalling(lastFuncID) {
						// 忽略包装
					} else if skip > 0 {
						skip--
					} else if n < max {
						(*[1 << 20]uintptr)(unsafe.Pointer(pcbuf))[n] = pc
						n++
					}
					lastFuncID = inltree[ix].funcID
					// 备份到“调用者”中的指令。
					tracepc = frame.fn.entry + uintptr(inltree[ix].parentPc)
					pc = tracepc + 1
				}
			}
			// 记录主帧。
			if f.funcID == funcID_wrapper && elideWrapperCalling(lastFuncID) {
				// 忽略包装函数（除非它们触发恐慌）。
			} else if skip > 0 {
				skip--
			} else if n < max {
				(*[1 << 20]uintptr)(unsafe.Pointer(pcbuf))[n] = pc
				n++
			}
			lastFuncID = f.funcID
			n-- // 偏移量n++低于
		}

		if printing {
			// 假设跳过=0进行打印。
			// 
			// 如果我们没有打印任何帧，请不要删除包装。不要省略
			// 调用panic的包装，而不要省略包装的
			// 函数。否则，请将它们排除在外。

			// 读取内联信息的备份到调用指令（逻辑与下面相同）
			tracepc := frame.pc
			if (n > 0 || flags&_TraceTrap == 0) && frame.pc > f.entry && !waspanic {
				tracepc--
			}
			// 如果有内联信息，则打印内部帧。
			if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
				inltree := (*[1 << 20]inlinedCall)(inldata)
				var inlFunc _func
				inlFuncInfo := funcInfo{&inlFunc, f.datap}
				for {
					ix := pcdatavalue(f, _PCDATA_InlTreeIndex, tracepc, nil)
					if ix < 0 {
						break
					}

					// 为
					// 内联函数创建一个伪函数。
					inlFunc.nameoff = inltree[ix].func_
					inlFunc.funcID = inltree[ix].funcID

					if (flags&_TraceRuntimeFrames) != 0 || showframe(inlFuncInfo, gp, nprint == 0, inlFuncInfo.funcID, lastFuncID) {
						name := funcname(inlFuncInfo)
						file, line := funcline(f, tracepc)
						print(name, "(...)\n")
						print("\t", file, ":", line, "\n")
						nprint++
					}
					lastFuncID = inltree[ix].funcID
					// 备份到“调用者”中的指令。
					tracepc = frame.fn.entry + uintptr(inltree[ix].parentPc)
				}
			}
			if (flags&_TraceRuntimeFrames) != 0 || showframe(f, gp, nprint == 0, f.funcID, lastFuncID) {
				// 崩溃时打印。
				// main（0x1，0x2，0x3）
				// /home/rsc/go/src/runtime/x.go:23+0xf 
				// 
				name := funcname(f)
				file, line := funcline(f, tracepc)
				if name == "runtime.gopanic" {
					name = "panic"
				}
				print(name, "(")
				argp := unsafe.Pointer(frame.argp)
				printArgs(f, argp)
				print(")\n")
				print("\t", file, ":", line)
				if frame.pc > f.entry {
					print(" +", hex(frame.pc-f.entry))
				}
				if gp.m != nil && gp.m.throwing > 0 && gp == gp.m.curg || level >= 2 {
					print(" fp=", hex(frame.fp), " sp=", hex(frame.sp), " pc=", hex(frame.pc))
				}
				print("\n")
				nprint++
			}
			lastFuncID = f.funcID
		}
		n++

		if f.funcID == funcID_cgocallback && len(cgoCtxt) > 0 {
			ctxt := cgoCtxt[len(cgoCtxt)-1]
			cgoCtxt = cgoCtxt[:len(cgoCtxt)-1]

			// skip仅适用于go帧。
			// 回调！=nil仅在我们只关心
			// 围棋框架时使用。
			if skip == 0 && callback == nil {
				n = tracebackCgoContext(pcbuf, printing, ctxt, n, max)
			}
		}

		waspanic = f.funcID == funcID_sigpanic
		injectedCall := waspanic || f.funcID == funcID_asyncPreempt

		// 请勿从堆栈底部展开。
		if !flr.valid() {
			break
		}

		// 展开到下一帧。
		frame.fn = flr
		frame.pc = frame.lr
		frame.lr = 0
		frame.sp = frame.fp
		frame.fp = 0
		frame.argmap = nil

		// 在链路寄存器体系结构上，sighandler在假装调用之前将LR保存在堆栈上。
		if usesLR && injectedCall {
			x := *(*uintptr)(unsafe.Pointer(frame.sp))
			frame.sp += alignUp(sys.MinFrameSize, sys.StackAlign)
			f = findfunc(frame.pc)
			frame.fn = f
			if !f.valid() {
				frame.pc = x
			} else if funcspdelta(f, frame.pc, &cache) == 0 {
				frame.lr = x
			}
		}
	}

	if printing {
		n = nprint
	}

	// 注意恐慌！=nil在这里没有问题：可能会有剩余的恐慌，因为恐慌堆栈上的延迟不会像延迟堆栈上的延迟那样按帧顺序嵌套。如果您有：
	// 
	// 第1帧延迟d1 
	// 第2帧延迟d2 
	// 第3帧延迟d3 
	// 第4帧延迟
	// 第4帧的紧急启动延迟
	// 第5帧，运行d3，延迟d4 
	// frame 5恐慌
	// frame 5的恐慌开始运行延迟
	// frame 6，运行d4，垃圾收集
	// frame 6，运行d2，垃圾收集
	// 
	// 在执行d4期间，恐慌堆栈为d4->d3，哪个
	// 嵌套正确，我们将把第3帧视为可恢复帧，因为我们
	// 可以找到d3。（事实上，第3帧是可恢复的。如果d4恢复
	// 并且第5帧继续运行，d3、d3可以恢复，我们将
	// 在（从）第3帧中恢复执行。）
	// 
	// 在执行d2期间，但恐慌堆栈是d2->d3，
	// 这是倒过来的。扫描会将d2匹配到第2帧，但在堆栈上有
	// d2，直到那时，这意味着它不会将d3匹配到第3帧。
	// 这没关系：如果我们正在运行d2，那么d2之后的所有延迟都已完成，并且它们对应的帧都已失效。对于第3帧，没有找到d3 
	// 意味着我们将设置第3帧的continpc==0，这是正确的
	// （第3帧是死的）。因此，在漫游结束时，紧急堆栈可以
	// 包含死帧的延迟（本例中为d3）。这里的反转
	// 总是表示一个死帧，而反转对
	// 扫描的影响是隐藏那些死帧，所以扫描仍然是正常的：
	// 在恐慌堆栈上留下的正是（并且只是）死帧。
	// 
	// 我们需要回拨！=此处为零，因为仅当回调时！=nil 
	// 我们知道gentraceback是在“必须正确”的上下文中调用的，而不是在“尽力而为”的上下文中调用的。
	// 回调只有在一切正常停止时才会发生。
	// 在其他情况下，例如为分析信号采集堆栈时，或者在崩溃期间打印回溯时，可能无法很好地停止一切
	// 并且堆栈漫游可能无法完成。
	if callback != nil && n < max && frame.sp != gp.stktopsp {
		print("runtime: g", gp.goid, ": frame.sp=", hex(frame.sp), " top=", hex(gp.stktopsp), "\n")
		print("\tstack=[", hex(gp.stack.lo), "-", hex(gp.stack.hi), "] n=", n, " max=", max, "\n")
		throw("traceback did not unwind completely")
	}

	return n
}

// printArgs在回溯中打印函数参数。
func printArgs(f funcInfo, argp unsafe.Pointer) {
	// 参数打印的“指令”用_FUNCDATA_ArgInfo编码。
	// 有关
	// 编码的说明，请参见cmd/compile/internal/ssagen.emitArgInfo。
	// 这些常量需要与编译器同步。
	const (
		_endSeq         = 0xff
		_startAgg       = 0xfe
		_endAgg         = 0xfd
		_dotdotdot      = 0xfc
		_offsetTooLarge = 0xfb
	)

	const (
		limit    = 10                       // 打印不超过10个参数/组件
		maxDepth = 5                        // 不超过5层嵌套
		maxLen   = (maxDepth*3+2)*limit + 1 // 最大_FUNCDATA_ArgInfo长度（请参见编译器端的说明）
	)

	p := (*[maxLen]uint8)(funcdata(f, _FUNCDATA_ArgInfo))
	if p == nil {
		return
	}

	print1 := func(off, sz uint8) {
		x := readUnaligned64(add(argp, uintptr(off)))
		// 屏蔽不相关位
		if sz < 8 {
			shift := 64 - sz*8
			if sys.BigEndian {
				x = x >> shift
			} else {
				x = x << shift >> shift
			}
		}
		print(hex(x))
	}

	start := true
	printcomma := func() {
		if !start {
			print(", ")
		}
	}
	pi := 0
printloop:
	for {
		o := p[pi]
		pi++
		switch o {
		case _endSeq:
			break printloop
		case _startAgg:
			printcomma()
			print("{")
			start = true
			continue
		case _endAgg:
			print("}")
		case _dotdotdot:
			printcomma()
			print("...")
		case _offsetTooLarge:
			printcomma()
			print("_")
		default:
			printcomma()
			sz := p[pi]
			pi++
			print1(o, sz)
		}
		start = false
	}
}

// reflectMethodValue是reflect.makeFuncImpl 
// 和reflect.methodValue的部分副本。
type reflectMethodValue struct {
	fn     uintptr
	stack  *bitvector // 参数和结果的ptrmap 
	argLen uintptr    // 仅参数
}

// getArgInfoFast返回调用f的参数帧信息。
// 它很短，并且可以内联。但是，它并不处理所有函数。
// 如果ok报告为false，则必须调用getArgInfo。
// TODO（josharian）：一旦我们进行了堆栈中的内联，
// 直接从getArgInfoFast调用getArgInfo并停止返回ok布尔值。
func getArgInfoFast(f funcInfo, needArgMap bool) (arglen uintptr, argmap *bitvector, ok bool) {
	return uintptr(f.args), nil, !(needArgMap && f.args == _ArgsSizeUnknown)
}

// getArgInfo返回调用f 
// 和调用帧帧的参数帧信息。
// 
// 这既用于具有活动堆栈帧的实际调用，也用于
// 尚未执行的延迟调用或goroutine。如果这是一个实际的
// 调用，则ctxt必须为nil（getArgInfo将从活动堆栈帧中检索所需内容）。如果这是一个延迟调用或未启动的goroutine，则
// ctxt必须是已延迟或go'd的函数对象。
func getArgInfo(frame *stkframe, f funcInfo, needArgMap bool, ctxt *funcval) (arglen uintptr, argmap *bitvector) {
	arglen = uintptr(f.args)
	if needArgMap && f.args == _ArgsSizeUnknown {
		// 从反射存根调用中提取反射存根的参数位图。
		switch funcname(f) {
		case "reflect.makeFuncStub", "reflect.methodValueCall":
			// 它们将*reflect.methodValue作为上下文寄存器。
			var mv *reflectMethodValue
			var retValid bool
			if ctxt != nil {
				// 这不是一个实际的调用，而是一个延迟调用或未启动的goroutine。
				// 函数值本身就是*reflect.methodValue。
				mv = (*reflectMethodValue)(unsafe.Pointer(ctxt))
			} else {
				// 这是一个真正的调用，它将
				// /*reflect.methodValue作为上下文
				// 注册并立即将其保存到0（SP）。从
				// 0（SP）获取methodValue。
				arg0 := frame.sp + sys.MinFrameSize
				mv = *(**reflectMethodValue)(unsafe.Pointer(arg0))
				// 判断返回值是否有效。
				// Reflect在复制返回值中的
				// 后会更新此值。
				retValid = *(*bool)(unsafe.Pointer(arg0 + 4*sys.PtrSize))
			}
			if mv.fn != f.entry {
				print("runtime: confused by ", funcname(f), "\n")
				throw("reflect mismatch")
			}
			bv := mv.stack
			arglen = uintptr(bv.n * sys.PtrSize)
			if !retValid {
				arglen = uintptr(mv.argLen) &^ (sys.PtrSize - 1)
			}
			argmap = bv
		}
	}
	return
}

// tracebackCgoContext处理从
// 上下文参数到setCgoTraceback的cgo上下文值的跟踪，用于gentraceback 
// 函数。它返回n的新值。
func tracebackCgoContext(pcbuf *uintptr, printing bool, ctxt uintptr, n, max int) int {
	var cgoPCs [32]uintptr
	cgoContextPCs(ctxt, cgoPCs[:])
	var arg cgoSymbolizerArg
	anySymbolized := false
	for _, pc := range cgoPCs {
		if pc == 0 || n >= max {
			break
		}
		if pcbuf != nil {
			(*[1 << 20]uintptr)(unsafe.Pointer(pcbuf))[n] = pc
		}
		if printing {
			if cgoSymbolizer == nil {
				print("non-Go function at pc=", hex(pc), "\n")
			} else {
				c := printOneCgoTraceback(pc, max-n, &arg)
				n += c - 1 // /+1下面几行
				anySymbolized = true
			}
		}
		n++
	}
	if anySymbolized {
		arg.pc = 0
		callCgoSymbolizer(&arg)
	}
	return n
}

func printcreatedby(gp *g) {
	// 显示是什么创建了goroutine，除了主goroutine（goid 1）。
	pc := gp.gopc
	f := findfunc(pc)
	if f.valid() && showframe(f, gp, false, funcID_normal, funcID_normal) && gp.goid != 1 {
		printcreatedby1(f, pc)
	}
}

func printcreatedby1(f funcInfo, pc uintptr) {
	print("created by ", funcname(f), "\n")
	tracepc := pc // 备份到funcline的调用指令。
	if pc > f.entry {
		tracepc -= sys.PCQuantum
	}
	file, line := funcline(f, tracepc)
	print("\t", file, ":", line)
	if pc > f.entry {
		print(" +", hex(pc-f.entry))
	}
	print("\n")
}

func traceback(pc, sp, lr uintptr, gp *g) {
	traceback1(pc, sp, lr, gp, 0)
}

// tracebacktrap类似于traceback，但希望PC和SP是从陷阱获得的，而不是从gp->sched或gp->syscallpc/gp->syscallsp或getcallersp/getcallersp。
// 由于它们来自陷阱而不是保存的对，
// 不得将初始PC倒转到上一条指令。
// （所有保存的对都记录了一个作为返回地址的PC，因此我们
// 将其回放到CALL指令中。）
// 如果gp.m.libcall{g，PC，sp}信息可用，它将优先使用该信息，而不是
// 传入的PC/sp/lr。
func tracebacktrap(pc, sp, lr uintptr, gp *g) {
	if gp.m.libcallsp != 0 {
		// 我们在某个地方的C代码中，从保存位置回溯。
		traceback1(gp.m.libcallpc, gp.m.libcallsp, 0, gp.m.libcallg.ptr(), 0)
		return
	}
	traceback1(pc, sp, lr, gp, _TraceTrap)
}

func traceback1(pc, sp, lr uintptr, gp *g, flags uint) {
	// 如果goroutine在cgo中，并且我们有cgo回溯，请打印出来。
	if iscgo && gp.m != nil && gp.m.ncgo > 0 && gp.syscallsp != 0 && gp.m.cgoCallers != nil && gp.m.cgoCallers[0] != 0 {
		// 锁定cgoCallers，使信号处理程序不会
		// 更改、复制阵列、重置阵列、解锁阵列。
		// 我们被锁定到线程，并且没有与信号处理程序同时运行
		// 。ABCFDG 
		atomic.Store(&gp.m.cgoCallersUse, 1)
		cgoCallers := *gp.m.cgoCallers
		gp.m.cgoCallers[0] = 0
		atomic.Store(&gp.m.cgoCallersUse, 0)

		printCgoTraceback(&cgoCallers)
	}

	var n int
	if readgstatus(gp)&^_Gscan == _Gsyscall {
		// 如果在系统调用中被阻止，则重写寄存器。
		pc = gp.syscallpc
		sp = gp.syscallsp
		flags &^= _TraceTrap
	}
	// 打印回溯。默认情况下，忽略运行时帧。
	// 如果这意味着我们什么也不打印，请重复强制打印所有帧。
	n = gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, flags)
	if n == 0 && (flags&_TraceRuntimeFrames) == 0 {
		n = gentraceback(pc, sp, lr, gp, 0, nil, _TracebackMaxFrames, nil, nil, flags|_TraceRuntimeFrames)
	}
	if n == _TracebackMaxFrames {
		print("...additional frames elided...\n")
	}
	printcreatedby(gp)

	if gp.ancestors == nil {
		return
	}
	for _, ancestor := range *gp.ancestors {
		printAncestorTraceback(ancestor)
	}
}

// printAncestorTraceback打印给定祖先的回溯。
// TODO:将其与gentraceback和CallersFrames统一起来。
func printAncestorTraceback(ancestor ancestorInfo) {
	print("[originating from goroutine ", ancestor.goid, "]:\n")
	for fidx, pc := range ancestor.pcs {
		f := findfunc(pc) // f先前验证的
		if showfuncinfo(f, fidx == 0, funcID_normal, funcID_normal) {
			printAncestorTracebackFuncInfo(f, pc)
		}
	}
	if len(ancestor.pcs) == _TracebackMaxFrames {
		print("...additional frames elided...\n")
	}
	// 显示创建goroutine的内容，主goroutine（goid 1）除外。
	f := findfunc(ancestor.gopc)
	if f.valid() && showfuncinfo(f, false, funcID_normal, funcID_normal) && ancestor.goid != 1 {
		printcreatedby1(f, ancestor.gopc)
	}
}

// printAncestorTraceback在给定的pc上打印给定的函数信息
// 在祖先回溯中。由于在创建呼叫方
// goroutine。
// goroutine时只能访问PC，因此此信息的精度降低了
func printAncestorTracebackFuncInfo(f funcInfo, pc uintptr) {
	name := funcname(f)
	if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
		inltree := (*[1 << 20]inlinedCall)(inldata)
		ix := pcdatavalue(f, _PCDATA_InlTreeIndex, pc, nil)
		if ix >= 0 {
			name = funcnameFromNameoff(f, inltree[ix].func_)
		}
	}
	file, line := funcline(f, pc)
	if name == "runtime.gopanic" {
		name = "panic"
	}
	print(name, "(...)\n")
	print("\t", file, ":", line)
	if pc > f.entry {
		print(" +", hex(pc-f.entry))
	}
	print("\n")
}

func callers(skip int, pcbuf []uintptr) int {
	sp := getcallersp()
	pc := getcallerpc()
	gp := getg()
	var n int
	systemstack(func() {
		n = gentraceback(pc, sp, 0, gp, skip, &pcbuf[0], len(pcbuf), nil, nil, 0)
	})
	return n
}

func gcallers(gp *g, skip int, pcbuf []uintptr) int {
	return gentraceback(^uintptr(0), ^uintptr(0), 0, gp, skip, &pcbuf[0], len(pcbuf), nil, nil, 0)
}

// showframe报告在回溯过程中是否应打印具有给定特征的帧
// 。
func showframe(f funcInfo, gp *g, firstFrame bool, funcID, childID funcID) bool {
	g := getg()
	if g.m.throwing > 0 && gp != nil && (gp == g.m.curg || gp == g.m.caughtsig.ptr()) {
		return true
	}
	return showfuncinfo(f, firstFrame, funcID, childID)
}

// showfuncinfo报告在回溯过程中是否应打印具有给定特征的函数。
func showfuncinfo(f funcInfo, firstFrame bool, funcID, childID funcID) bool {
	// 注意，f可能是内联
	// 函数的合成funcInfo，在这种情况下，只设置了nameoff和funcID。

	level, _, _ := gotraceback()
	if level > 1 {
		// 显示所有帧。ABCFDG 
		return true
	}

	if !f.valid() {
		return false
	}

	if funcID == funcID_wrapper && elideWrapperCalling(childID) {
		return false
	}

	name := funcname(f)

	// 见golang.org/issue/5832。
	if name == "runtime.gopanic" && !firstFrame {
		return true
	}

	return bytealg.IndexByteString(name, '.') >= 0 && (!hasPrefix(name, "runtime.") || isExportedRuntime(name))
}

// isExportedRuntime报告名称是否为导出的运行时函数。
// 它只用于运行时函数，所以ASCII A-Z是可以的。
func isExportedRuntime(name string) bool {
	const n = len("runtime.")
	return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z'
}

// elideWrapperCalling报告是否应从堆栈跟踪中删除调用
// 函数id的包装函数。
func elideWrapperCalling(id funcID) bool {
	// 如果包装器调用了一个panic函数而不是
	// 包装的函数，我们希望将其包含在堆栈中。
	return !(id == funcID_gopanic || id == funcID_sigpanic || id == funcID_panicwrap)
}

var gStatusStrings = [...]string{
	_Gidle:      "idle",
	_Grunnable:  "runnable",
	_Grunning:   "running",
	_Gsyscall:   "syscall",
	_Gwaiting:   "waiting",
	_Gdead:      "dead",
	_Gcopystack: "copystack",
	_Gpreempted: "preempted",
}

func goroutineheader(gp *g) {
	gpstatus := readgstatus(gp)

	isScan := gpstatus&_Gscan != 0
	gpstatus &^= _Gscan // 删除扫描位

	// 基本字符串状态
	var status string
	if 0 <= gpstatus && gpstatus < uint32(len(gStatusStrings)) {
		status = gStatusStrings[gpstatus]
	} else {
		status = "???"
	}

	// 覆盖。
	if gpstatus == _Gwaiting && gp.waitreason != waitReasonZero {
		status = gp.waitreason.String()
	}

	// 大约G被阻止的时间，以分钟为单位
	var waitfor int64
	if (gpstatus == _Gwaiting || gpstatus == _Gsyscall) && gp.waitsince != 0 {
		waitfor = (nanotime() - gp.waitsince) / 60e9
	}
	print("goroutine ", gp.goid, " [", status)
	if isScan {
		print(" (scan)")
	}
	if waitfor >= 1 {
		print(", ", waitfor, " minutes")
	}
	if gp.lockedm != 0 {
		print(", locked to thread")
	}
	print("]:\n")
}

func tracebackothers(me *g) {
	level, _, _ := gotraceback()

	// 如果我们还没有阻止，请先显示当前的goroutine。
	curgp := getg().m.curg
	if curgp != nil && curgp != me {
		print("\n")
		goroutineheader(curgp)
		traceback(^uintptr(0), ^uintptr(0), 0, curgp)
	}

	// 我们不能在这里调用locking forEachG，因为这可能是在致命的
	// 抛出/恐慌期间，锁定可能出现故障或直接的
	// 死锁。
	// 
	// 相反，请使用forEachGRace，它不需要锁定。我们不锁定
	// 以防止并发创建新的Gs，但即使使用allglock，我们也可能会错过此循环后创建的Gs。
	forEachGRace(func(gp *g) {
		if gp == me || gp == curgp || readgstatus(gp) == _Gdead || isSystemGoroutine(gp, false) && level < 2 {
			return
		}
		print("\n")
		goroutineheader(gp)
		// 注意：当tracebackothers为
		// 时，gp.m==g.m发生在
		// 系统堆栈调用期间启动的信号处理程序中。原来的G仍然处于
		// 运行状态，我们想打印它的堆栈。
		if gp.m != getg().m && readgstatus(gp)&^_Gscan == _Grunning {
			print("\tgoroutine running on other thread; stack unavailable\n")
			printcreatedby(gp)
		} else {
			traceback(^uintptr(0), ^uintptr(0), 0, gp)
		}
	})
}

// tracebackhextump hextump在frame.sp和frame.fp周围转储部分stk 
// 用于调试目的。如果地址bad包含在
// HEXDUMPING范围内，它也会标记它。
func tracebackHexdump(stk stack, frame *stkframe, bad uintptr) {
	const expand = 32 * sys.PtrSize
	const maxExpand = 256 * sys.PtrSize
	// 从frame.sp开始。
	lo, hi := frame.sp, frame.sp
	// 展开以包括frame.fp。
	if frame.fp != 0 && frame.fp < lo {
		lo = frame.fp
	}
	if frame.fp != 0 && frame.fp > hi {
		hi = frame.fp
	}
	// 再扩展一点。
	lo, hi = lo-expand, hi+expand
	// 但不要离frame.sp太远。
	if lo < frame.sp-maxExpand {
		lo = frame.sp - maxExpand
	}
	if hi > frame.sp+maxExpand {
		hi = frame.sp + maxExpand
	}
	// 并且不要超出堆栈边界。
	if lo < stk.lo {
		lo = stk.lo
	}
	if hi > stk.hi {
		hi = stk.hi
	}

	// 打印十六进制转储。
	print("stack: frame={sp:", hex(frame.sp), ", fp:", hex(frame.fp), "} stack=[", hex(stk.lo), ",", hex(stk.hi), ")\n")
	hexdumpWords(lo, hi, func(p uintptr) byte {
		switch p {
		case frame.fp:
			return '>'
		case frame.sp:
			return '<'
		case bad:
			return '!'
		}
		return 0
	})
}

// isSystemGoroutine报告是否必须在堆栈转储和死锁检测器中忽略goroutine g
// 在运行时启动的任何goroutine.*入口点，除了runtime.main、
// runtime.handleSyncEvent（仅限wasm）和有时runtime.runfinq。
// 
// 如果fixed为true，则用户和
// 系统（即终结器GOROUTE）之间可能存在差异的任何GOROUTE都被视为用户
// GOROUTE。
func isSystemGoroutine(gp *g, fixed bool) bool {
	// 将此文件与cmd/trace/trace.go:isSystemGoroutine保持同步。
	f := findfunc(gp.startpc)
	if !f.valid() {
		return false
	}
	if f.funcID == funcID_runtime_main || f.funcID == funcID_handleAsyncEvent {
		return false
	}
	if f.funcID == funcID_runfinq {
		// 如果终结器goroutine正在调用
		// 则将其包含在用户代码中。
		if fixed {
			// 此goroutine可能会有所不同。在固定模式下，ABCFDG 
			return false
		}
		return !fingRunning
	}
	return hasPrefix(funcname(f), "runtime.")
}

// SetCgoTraceback记录三个C函数，用于从C代码收集
// 回溯信息，并将回溯
// 信息转换为符号信息。这些用于打印
// 使用cgo的程序的堆栈跟踪。
// 
// 可以从信号
// 处理程序调用回溯和上下文函数，因此必须仅使用异步信号安全函数。
// 当程序为
// 崩溃，因此必须小心使用内存。
// 函数都不能回调到Go中。
// 
// 将使用单个参数调用上下文函数，一个
// 指向结构的指针：
// 
// struct{
// context uintptpr 
// /}
// 
// 使用C语法，此结构将是
// 
// struct{
// /uintptpr\t Context；
// ；
// 
// 如果上下文字段为0，则调用上下文函数以记录当前回溯上下文。它应该在
// 上下文字段中记录有关当前
// 执行点的任何信息，以便以后生成堆栈跟踪，可能是
// 堆栈指针和PC。在这种情况下，上下文函数将从C代码调用
// 。
// 
// 如果上下文字段不是0，则它是
// 上一次调用上下文函数返回的值。当不再需要
// 上下文时调用此案例；也就是说，当Go代码将
// 返回给其C代码调用方时。这允许上下文函数释放
// 任何相关资源。
// 
// 虽然上下文函数记录
// 在调用堆栈跟踪时完成堆栈跟踪是正确的，但只需在回溯函数中复制该
// 即可，在一个典型的程序中，将多次调用上下文
// 函数，而不会记录该上下文的
// 回溯。在
// 上下文函数调用中记录完整的堆栈跟踪可能效率低下。
// 
// 将使用单个参数调用回溯函数，即
// 指向结构的指针：
// 
// 结构{
// Context-uintpttr-
// SigContext-uintpttr-
// Buf*uintpttr-
// Max-uintpttr-
// /}
// /
// 在C语法中，此结构将是
// {
// uintptr\t上下文；
// uintptr\t SigContext；
// uintptr\t*Buf；
// uintptr\t Max；
// ；
// 
// 上下文字段将为零，以从
// 当前程序执行点收集回溯。在这种情况下，将从C代码调用回溯
// 函数。
// 
// 否则上下文将是ca先前返回的值ll to 
// 上下文函数。回溯函数应从程序执行中保存的点收集堆栈
// 跟踪。回溯
// 函数可以从记录上下文的
// 以外的执行线程调用，但仅当上下文为k时已知为
// 有效且不变。回溯函数也可以在记录
// 更深。回溯函数可以使用相同的上下文值多次调用；它通常适用于cache 
// 上下文的同一线程上的调用堆栈中调用
// 结果，如果可能，第一次为特定的
// 上下文值调用此函数。
// 
// 如果从Unix 
// 系统上的信号处理程序调用回溯函数，则SigContext将是传递给
// 的信号上下文参数信号处理程序（C ucontext_t*转换为uintptr_t）。这可能是
// 用于在信号发生的点开始跟踪。如果
// 没有从信号处理程序调用回溯函数，
// SigContext将为零。
// 
// Buf是应存储回溯信息的位置。它应该是
// 是PC值，这样Buf[0]就是调用方的PC，Buf[1]是
// 该函数调用方的PC，依此类推。Max是要存储的最大
// 条目数。函数应该存储一个零到
// 表示堆栈的顶部，或者调用方位于不同的
// 堆栈上，可能是Go堆栈。
// 
// 将调用
// 指令的文件/行返回给symboler函数。不需要额外的减法，也不适合。
// 
// 在所有平台上，当来自
// Go to C to Go的调用请求堆栈跟踪时，将调用traceback函数。在linux/amd64、linux/ppc64le、
// 和freebsd/amd64上，当执行cgo调用的线程接收到
// 信号时，也会调用回溯函数。
// 回溯函数不应假设何时调用
// 因为Go的未来版本可能会进行其他调用。
// 
// 将使用单个参数调用Symboler函数，一个
// 指向结构的指针：
// 
// struct{
// PC uintptr
// 文件*字节
// 行号uintptr
// 函数*字节
// 输入uintptr
// 更多uintptr
// 数据uintptr
// /}
// 
// 使用C语法，此结构将是
// 
// struct{
// uintptr\t PC；
// char*文件；
// uintptr\t行号；
// char*函数；
// uintptr\t条目；
// uintptr\t更多；
// uintptr\t数据；
// 
// PC字段将是通过调用回溯
// 函数返回的值。
// 
// 第一次为特定回溯调用函数时，
// 除PC之外的所有字段都将为0。函数应填写
// 如果可能，其他字段，如果信息
// 不可用，则将其设置为0/nil。数据字段可用于存储调用中任何有用的
// 信息。更多字段应设置为非零
// 如果此电脑有更多信息，则为零。如果更多
// 设置为非零，将使用相同的
// PC再次调用该函数，并可能返回不同的信息（这是为了将
// 与内联函数一起使用）。如果“更多”为零，则该函数将被
// 调用，并使用回溯中的下一个PC值。当回溯
// 完成时，将再次调用该函数，并将PC设置为
// 零；这可用于释放任何信息。每次调用都将
// 离开结构的字段t设置为与
// 返回时相同的值，但当“更多”字段为零时，PC字段除外。
// 函数不能在调用之间保留结构指针的副本。
// 
// 调用SetCgoTraceback时，版本参数为版本
// 编号函数预期接收的结构的。
// 当前该值必须为零。
// 
// 符号器函数可能为零，在这种情况下，
// 回溯函数的结果将显示为数字。如果
// 回溯函数为零，则符号为零izer函数永远不会被
// 调用。上下文函数可能为nil，在这种情况下，只会在上下文字段
// 回溯函数。如果上下文函数为nil，则从Go到C 
// 设置为零的情况下调用
// to Go的调用将不会显示对C端口的回溯调用堆栈的初始化。
// 
// SetCgoTraceback应该只调用一次，最好是从init函数调用。
func SetCgoTraceback(version int, traceback, context, symbolizer unsafe.Pointer) {
	if version != 0 {
		panic("unsupported version")
	}

	if cgoTraceback != nil && cgoTraceback != traceback ||
		cgoContext != nil && cgoContext != context ||
		cgoSymbolizer != nil && cgoSymbolizer != symbolizer {
		panic("call SetCgoTraceback only once")
	}

	cgoTraceback = traceback
	cgoContext = context
	cgoSymbolizer = symbolizer

	// C函数调用Go 
	// 函数时调用上下文函数。因此，它仅由运行时/cgo中的C代码调用。
	if _cgo_set_context_function != nil {
		cgocall(_cgo_set_context_function, context)
	}
}

var cgoTraceback unsafe.Pointer
var cgoContext unsafe.Pointer
var cgoSymbolizer unsafe.Pointer

// cgoTracebackArg是t传递给cgoTraceback的类型。
type cgoTracebackArg struct {
	context    uintptr
	sigContext uintptr
	buf        *uintptr
	max        uintptr
}

// cgoContextArg是传递给上下文函数的类型。
type cgoContextArg struct {
	context uintptr
}

// cgoSymbolizerArg是传递给cgoSymbolizerArg的类型。
type cgoSymbolizerArg struct {
	pc       uintptr
	file     *byte
	lineno   uintptr
	funcName *byte
	entry    uintptr
	more     uintptr
	data     uintptr
}

// cgoTraceback打印调用方的回溯。
func printCgoTraceback(callers *cgoCallers) {
	if cgoSymbolizer == nil {
		for _, c := range callers {
			if c == 0 {
				break
			}
			print("non-Go function at pc=", hex(c), "\n")
		}
		return
	}

	var arg cgoSymbolizerArg
	for _, c := range callers {
		if c == 0 {
			break
		}
		printOneCgoTraceback(c, 0x7fffffff, &arg)
	}
	arg.pc = 0
	callCgoSymbolizer(&arg)
}

// printOneCgoTraceback打印单个cgo调用方的回溯。
// 由于内联，这可以打印多行。
// 返回打印的帧数。
func printOneCgoTraceback(pc uintptr, max int, arg *cgoSymbolizerArg) int {
	c := 0
	arg.pc = pc
	for c <= max {
		callCgoSymbolizer(arg)
		if arg.funcName != nil {
			// 注意，我们不打印任何参数
			// 这里的信息，连括号都没有。
			// 符号器必须在适当的情况下添加该符号。
			println(gostringnocopy(arg.funcName))
		} else {
			println("non-Go function")
		}
		print("\t")
		if arg.file != nil {
			print(gostringnocopy(arg.file), ":", arg.lineno, " ")
		}
		print("pc=", hex(pc), "\n")
		c++
		if arg.more == 0 {
			break
		}
	}
	return c
}

// callCgoSymbolizer调用cgoSymbolizer函数。
func callCgoSymbolizer(arg *cgoSymbolizerArg) {
	call := cgocall
	if panicking > 0 || getg().m.curg != getg() {
		// 我们不希望在系统堆栈中出现恐慌时调用调度程序。
		call = asmcgocall
	}
	if msanenabled {
		msanwrite(unsafe.Pointer(arg), unsafe.Sizeof(cgoSymbolizerArg{}))
	}
	call(cgoSymbolizer, noescape(unsafe.Pointer(arg)))
}

// cgoContextPCs从cgo回溯中获取PC值。
func cgoContextPCs(ctxt uintptr, buf []uintptr) {
	if cgoTraceback == nil {
		return
	}
	call := cgocall
	if panicking > 0 || getg().m.curg != getg() {
		// 我们不希望在系统堆栈中出现恐慌时调用调度程序。
		call = asmcgocall
	}
	arg := cgoTracebackArg{
		context: ctxt,
		buf:     (*uintptr)(noescape(unsafe.Pointer(&buf[0]))),
		max:     uintptr(len(buf)),
	}
	if msanenabled {
		msanwrite(unsafe.Pointer(&arg), unsafe.Sizeof(arg))
	}
	call(cgoTraceback, noescape(unsafe.Pointer(&arg)))
}
